import tkinter as tk
from tkinter import ttk, filedialog, messagebox, font
from tkinter.scrolledtext import ScrolledText
import os
import sys
import chardet


class TextEditor:
    def __init__(self, root):
        self.root = root
        self.root.title("文本编辑器")
        self.root.geometry("800x600")

        # 默认设置
        self.current_file = None
        self.file_encoding = "UTF-8"

        # 创建菜单栏
        self.create_menu()

        # 创建主框架
        self.main_frame = ttk.Frame(root)
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # 创建文本编辑区域容器
        self.text_container = ttk.Frame(self.main_frame)
        self.text_container.pack(fill=tk.BOTH, expand=True)

        # 创建文本区域
        self.text_area = ScrolledText(
            self.text_container, undo=True, font=("宋体", 16)
        )
        self.text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 状态栏
        self.status_bar = ttk.Label(root, text="就绪", relief=tk.SUNKEN, anchor=tk.W)
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        # 绑定事件
        self.text_area.bind("<KeyRelease>", self.update_status)
        self.text_area.bind("<ButtonRelease-1>", self.update_status)
        self.text_area.bind("<<Modified>>", self.on_text_modified)
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

        # 绑定滚动事件
        self.text_area.bind("<MouseWheel>", self.on_mousewheel)  # Windows
        self.text_area.bind("<Button-4>", self.on_mousewheel)  # Linux向上
        self.text_area.bind("<Button-5>", self.on_mousewheel)  # Linux向下

        # 检查命令行参数
        if len(sys.argv) > 1:
            self.open_file(sys.argv[1])

    def create_menu(self):
        menubar = tk.Menu(self.root)

        # 文件菜单
        filemenu = tk.Menu(menubar, tearoff=0)
        filemenu.add_command(label="新建", command=self.new_file, accelerator="Ctrl+N")
        filemenu.add_command(
            label="打开...", command=self.open_file, accelerator="Ctrl+O"
        )
        filemenu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
        filemenu.add_command(
            label="另存为...", command=self.save_as, accelerator="Ctrl+Shift+S"
        )
        filemenu.add_separator()
        filemenu.add_command(label="退出", command=self.on_closing)
        menubar.add_cascade(label="文件", menu=filemenu)

        # 编辑菜单
        editmenu = tk.Menu(menubar, tearoff=0)
        editmenu.add_command(label="撤销", command=self.undo, accelerator="Ctrl+Z")
        editmenu.add_command(label="重做", command=self.redo, accelerator="Ctrl+Y")
        editmenu.add_separator()
        editmenu.add_command(label="剪切", command=self.cut, accelerator="Ctrl+X")
        editmenu.add_command(label="复制", command=self.copy, accelerator="Ctrl+C")
        editmenu.add_command(label="粘贴", command=self.paste, accelerator="Ctrl+V")
        editmenu.add_separator()
        editmenu.add_command(
            label="替换...", command=self.replace, accelerator="Ctrl+H"
        )
        menubar.add_cascade(label="编辑", menu=editmenu)

        # 编码菜单
        encodingmenu = tk.Menu(menubar, tearoff=0)
        encodings = [
            ("UTF-8", "utf-8"),
            ("UTF-8-SIG", "utf-8-sig"),
            ("GBK", "gbk"),
            ("GB2312", "gb2312"),
            ("Big5", "big5"),
            ("ISO-8859-1", "iso-8859-1"),
        ]
        for label, encoding in encodings:
            encodingmenu.add_command(
                label=label, command=lambda e=encoding: self.change_encoding(e)
            )
        menubar.add_cascade(label="编码", menu=encodingmenu)

        # 字体菜单
        fontmenu = tk.Menu(menubar, tearoff=0)
        font_sizes = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24]
        for size in font_sizes:
            fontmenu.add_command(
                label=str(size), command=lambda s=size: self.change_font_size(s)
            )
        menubar.add_cascade(label="字体大小", menu=fontmenu)

        self.root.config(menu=menubar)

        # 设置快捷键
        self.root.bind("<Control-n>", lambda event: self.new_file())
        self.root.bind("<Control-o>", lambda event: self.open_file())
        self.root.bind("<Control-s>", lambda event: self.save_file())
        self.root.bind("<Control-Shift-S>", lambda event: self.save_as())
        self.root.bind("<Control-z>", lambda event: self.undo())
        self.root.bind("<Control-y>", lambda event: self.redo())
        self.root.bind("<Control-x>", lambda event: self.cut())
        self.root.bind("<Control-c>", lambda event: self.copy())
        self.root.bind("<Control-v>", lambda event: self.paste())
        self.root.bind("<Control-h>", lambda event: self.replace())

    def on_mousewheel(self, event):
        """鼠标滚轮事件处理"""
        # Windows鼠标滚轮事件
        if hasattr(event, "delta"):
            # 修正滚轮方向为自然滚动
            scroll_direction = -1 if event.delta > 0 else 1
            self.text_area.yview_scroll(scroll_direction, "units")
            return "break"

        # Linux鼠标滚轮事件
        if event.num == 4:  # 向上滚动
            self.text_area.yview_scroll(-1, "units")
            return "break"
        elif event.num == 5:  # 向下滚动
            self.text_area.yview_scroll(1, "units")
            return "break"

        return None

    def get_text_stats(self):
        """获取文本统计信息"""
        cursor_pos = self.text_area.index(tk.INSERT)
        line, col = cursor_pos.split(".")

        # 计算总行数
        lines = self.text_area.index(tk.END).split(".")[0]
        total_lines = int(lines) - 1

        # 计算字符数
        content = self.text_area.get(1.0, tk.END)
        char_count = len(content) - 1  # 减去尾部的换行符

        # 检测换行符类型
        if "\r\n" in content:
            newline_type = "CRLF (Windows)"
        elif "\r" in content:
            newline_type = "CR (Mac)"
        else:
            newline_type = "LF (Unix)"

        return {
            "line": line,
            "column": col,
            "total_lines": total_lines,
            "char_count": char_count,
            "encoding": self.file_encoding,
            "newline_type": newline_type,
        }

    def update_status(self, event=None):
        """更新状态栏"""
        stats = self.get_text_stats()
        status_text = (
            f"行: {stats['line']}, 列: {stats['column']} | "
            f"总行数: {stats['total_lines']} | "
            f"字符数: {stats['char_count']} | "
            f"编码: {stats['encoding']} | "
            f"换行符: {stats['newline_type']}"
        )
        self.status_bar.config(text=status_text)

    def on_text_modified(self, event=None):
        """处理文本修改事件"""
        if self.text_area.edit_modified():
            self.text_area.edit_modified(False)
            self.update_status()
        return "break"

    def new_file(self):
        """创建新文件"""
        if self.text_area.get(1.0, tk.END).strip() != "":
            if messagebox.askyesno("保存", "是否在创建新文件前保存当前文件？"):
                self.save_file()

        self.text_area.delete(1.0, tk.END)
        self.text_area.edit_reset()  # 重置撤销历史
        self.current_file = None
        self.file_encoding = "UTF-8"
        self.root.title("文本编辑器 - 新文件")
        self.update_status()
        self.text_area.focus_set()

    def open_file(self, filename=None):
        """打开文件"""
        if not filename:
            filename = filedialog.askopenfilename(
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
            )
            if not filename:
                return

        try:
            # 使用chardet检测文件编码
            with open(filename, "rb") as file:
                raw_data = file.read()

            if not raw_data:
                self.text_area.delete(1.0, tk.END)
                self.text_area.edit_reset()  # 重置撤销历史
                self.current_file = filename
                self.root.title(f"文本编辑器 - {os.path.basename(filename)}")
                self.update_status()
                return

            detection = chardet.detect(raw_data)
            detected_encoding = detection["encoding"] or "utf-8"
            # confidence = detection["confidence"]

            # 尝试使用检测到的编码解码
            try:
                content = raw_data.decode(detected_encoding)
            except Exception:
                # 尝试其他常用编码
                encodings = ["utf-8", "gbk", "big5", "latin1"]
                for encoding in encodings:
                    try:
                        content = raw_data.decode(encoding)
                        detected_encoding = encoding
                        break
                    except Exception:
                        continue

            self.text_area.delete(1.0, tk.END)
            self.text_area.insert(1.0, content)
            self.text_area.edit_reset()  # 重置撤销历史
            self.current_file = filename
            self.file_encoding = detected_encoding
            self.root.title(f"文本编辑器 - {os.path.basename(filename)}")
            self.update_status()

            # messagebox.showinfo(
            #     "编码信息", f"检测到编码: {detected_encoding}\n置信度: {confidence:.2%}"
            # )

        except Exception as e:
            messagebox.showerror("错误", f"打开文件失败: {str(e)}")
        finally:
            self.text_area.edit_modified(False)

    def save_file(self):
        """保存文件"""
        if not self.current_file:
            self.save_as()
        else:
            self._save_to_file(self.current_file)

    def save_as(self):
        """另存为文件"""
        filename = filedialog.asksaveasfilename(
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")],
        )
        if filename:
            self._save_to_file(filename)
            self.current_file = filename
            self.root.title(f"文本编辑器 - {os.path.basename(filename)}")

    def _save_to_file(self, filename):
        """实际保存文件的方法"""
        try:
            content = self.text_area.get(1.0, tk.END)

            # 移除最后一个换行符（Tkinter自动添加的）
            if content.endswith("\n"):
                content = content[:-1]

            with open(filename, "w", encoding=self.file_encoding) as file:
                file.write(content)

            self.text_area.edit_modified(False)
            messagebox.showinfo("保存成功", f"文件已保存: {filename}")
        except Exception as e:
            messagebox.showerror("保存失败", f"保存文件时出错: {str(e)}")

    def change_encoding(self, new_encoding):
        """更改文件编码"""
        if not self.text_area.get(1.0, tk.END).strip():
            self.file_encoding = new_encoding
            self.update_status()
            return

        try:
            # 获取当前内容
            content = self.text_area.get(1.0, tk.END)

            # 从旧编码转换到新编码
            try:
                if self.file_encoding.lower() != new_encoding.lower():
                    # 重新编码内容
                    content = content.encode(self.file_encoding).decode(new_encoding)
            except UnicodeError:
                pass

            # 更新文本区域
            self.text_area.delete(1.0, tk.END)
            self.text_area.insert(1.0, content)
            self.text_area.edit_reset()  # 重置撤销历史

            # 更新编码
            self.file_encoding = new_encoding
            self.update_status()

            messagebox.showinfo("编码更改", f"文件编码已更改为: {new_encoding}")

        except Exception as e:
            messagebox.showerror("错误", f"更改编码失败: {str(e)}")

    def edit_command(self, command):
        """执行编辑命令"""
        try:
            if command == "undo":
                self.text_area.edit_undo()
            elif command == "redo":
                self.text_area.edit_redo()
            elif command == "cut":
                self.text_area.event_generate("<<Cut>>")
            elif command == "copy":
                self.text_area.event_generate("<<Copy>>")
            elif command == "paste":
                self.text_area.event_generate("<<Paste>>")
        except tk.TclError:
            pass  # 没有可撤销/重做的操作时忽略错误

    def undo(self):
        self.edit_command("undo")

    def redo(self):
        self.edit_command("redo")

    def cut(self):
        self.edit_command("cut")

    def copy(self):
        self.edit_command("copy")

    def paste(self):
        self.edit_command("paste")

    def replace(self):
        """替换对话框"""
        dialog = tk.Toplevel(self.root)
        dialog.title("替换")
        dialog.transient(self.root)
        dialog.grab_set()
        dialog.geometry("400x180")
        dialog.resizable(False, False)

        # 主内容框架
        content_frame = ttk.Frame(dialog)
        content_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

        # 查找内容标签和输入框
        ttk.Label(content_frame, text="查找内容:").grid(row=0, column=0, sticky="w")
        self.search_entry = ttk.Entry(content_frame, width=30)
        self.search_entry.grid(row=0, column=1, padx=(5, 0), sticky="ew")

        # 替换为标签和输入框
        ttk.Label(content_frame, text="替换为:").grid(
            row=1, column=0, sticky="w", pady=(5, 0)
        )
        self.replace_entry = ttk.Entry(content_frame, width=30)
        self.replace_entry.grid(row=1, column=1, padx=(5, 0), sticky="ew", pady=(5, 10))

        # 区分大小写复选框 - 单独放在一行
        self.case_var_replace = tk.BooleanVar()
        case_check = ttk.Checkbutton(
            content_frame, text="区分大小写", variable=self.case_var_replace
        )
        case_check.grid(row=2, column=0, columnspan=2, sticky="w")

        # 按钮框架
        button_frame = ttk.Frame(content_frame)
        button_frame.grid(row=3, column=0, columnspan=2, pady=(10, 0))

        # 添加查找、替换、关闭按钮
        ttk.Button(button_frame, text="查找下一个", command=self.replace_next).grid(
            row=0, column=0
        )
        ttk.Button(
            button_frame,
            text="替换",
            command=lambda: self.replace_selected(self.replace_entry.get()),
        ).grid(row=0, column=1)
        ttk.Button(button_frame, text="全部替换", command=self.replace_all).grid(
            row=0, column=2
        )
        ttk.Button(button_frame, text="关闭", command=dialog.destroy).grid(
            row=0, column=3
        )

    def replace_next(self):
        """在替换对话框中查找下一个"""
        self.text_area.tag_remove("found", "1.0", tk.END)

        search_text = self.search_entry.get()
        if not search_text:
            return

        start_idx = "1.0"
        if self.text_area.tag_ranges("sel"):
            start_idx = self.text_area.index("sel.last")

        pos = self.text_area.search(
            search_text,
            start_idx,
            stopindex=tk.END,
            nocase=not self.case_var_replace.get(),
        )

        if pos:
            end_pos = f"{pos}+{len(search_text)}c"
            self.text_area.tag_add("found", pos, end_pos)
            self.text_area.tag_add("sel", pos, end_pos)
            self.text_area.mark_set(tk.INSERT, end_pos)
            self.text_area.see(pos)
        else:
            messagebox.showinfo("替换", "找不到匹配项")

    def replace_selected(self, replace_text):
        """替换选中的文本"""
        if self.text_area.tag_ranges("sel"):
            self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)
            self.text_area.insert(tk.INSERT, replace_text)

    def replace_all(self):
        """替换所有匹配项"""
        search_text = self.search_entry.get()
        replace_text = self.replace_entry.get()

        if not search_text:
            return

        # 移动到文本开头
        self.text_area.mark_set("insert", "1.0")
        self.text_area.tag_remove("found", "1.0", tk.END)

        count = 0
        while True:
            pos = self.text_area.search(
                search_text,
                "insert",
                stopindex=tk.END,
                count=len(search_text),
                nocase=not self.case_var_replace.get(),
            )
            if not pos:
                break

            end_pos = f"{pos}+{len(search_text)}c"
            self.text_area.delete(pos, end_pos)
            self.text_area.insert(pos, replace_text)
            self.text_area.mark_set("insert", f"{pos}+{len(replace_text)}c")
            count += 1

        if count == 0:
            messagebox.showinfo("替换", "未找到匹配项")
        else:
            messagebox.showinfo("替换", f"已替换 {count} 处匹配项")

    def change_font_size(self, size):
        """更改字体大小"""
        current_font = font.Font(font=self.text_area["font"])
        font_family = current_font.actual()["family"]
        self.text_area.config(font=(font_family, size))

    def on_closing(self):
        """关闭程序时的处理"""
        if self.text_area.edit_modified():
            if not messagebox.askyesnocancel("保存", "文档已修改，是否保存？"):
                return
            self.save_file()
        self.root.destroy()


if __name__ == "__main__":
    root = tk.Tk()
    app = TextEditor(root)
    root.mainloop()
